%{
#include <scdl/parser/common.hpp>
#include <scdl/parser/internal.hpp>
#include <scdl/parser/StringMap.hpp>
#include "parser.hpp"

#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
using std::cout;
using std::cerr;
using namespace scdl;

#ifdef WIN32
#define strdup _strdup
#define snprintf _snprintf
#endif

char *importPath[] = {
        "scdl/import/",
        "",
        0
};

struct bufstack {
        struct bufstack *prev;

        /* Fields for the older */
        YY_BUFFER_STATE bs;
        int lineno;
        char *filename;

        /* Fields for the newer */
        FILE *f;
} *curbs = 0;

char *curfilename = "<builtin>";

#define FILELIST_BUF_SIZE 1024
struct filelist {
        char buf[FILELIST_BUF_SIZE];
        int used;
        struct filelist *next;
} *filelists = 0;

int checkfile(char *fn);
int newfile(char *fn);
int popfile(void);
void finalize(void);

int yycolumn = 1;

#define YY_USER_ACTION yylloc.first_line = yylloc.last_line = yylineno; \
                       yylloc.first_column = yycolumn; yylloc.last_column = yycolumn + yyleng - 1; \
                       yycolumn += yyleng;

%}

%option noyywrap
%option nounistd
%option yylineno
%option never-interactive

%x COMMENT
%x IFILE

IDENTIFIER              [A-Za-z][A-Za-z0-9_]*

%%

 /* Comments */
^"(*"                   { BEGIN COMMENT; }
<COMMENT>"*)"           { BEGIN INITIAL; }
<COMMENT>\n             { yycolumn = 1; }
<COMMENT>([^*])+|.
<COMMENT><<EOF>>        { cerr << "Unterminated comment\n"; exit(1); }

 /* Keywords */
Assume                  { return ASSUME; }
Define                  { return DEFINE; }
Bind                    { return BIND; }
Inductive               { return INDUCTIVE; }
Record                  { return RECORD; }
Import                  { BEGIN IFILE; }

"(" |
")" |
"[" |
"]" |
"{" |
"}" |
":" |
"|" |
"," |
"."                     { return yytext[0]; }
"+" |
"-" |
"*" |
"/"                     { yylval.sym = yytext[0]; return OP; }
"->"                    { return PRODUCT; }
":="                    { return ASSIGN; }
".."                    { return STEP; }

true                    { yylval.bval = true; return BOOL; }
false                   { yylval.bval = false; return BOOL; }
[+-]?(0|[1-9][0-9]*)    { yylval.number = atof(yytext); return NUMBER; }
[+-]?([0-9]*\.[0-9]+)   { yylval.number = atof(yytext); return NUMBER; }

 /* File Importing */
<IFILE>[^ \t\n\".]+     {
                        int ret = newfile(yytext);
                        if (ret < 0) {
                                finalize(); yyterminate();
                        } else if (ret > 0)
                                BEGIN INITIAL;
                        }

<IFILE>"."              { BEGIN INITIAL; }

<<EOF>>                 { if (popfile()) { BEGIN IFILE; } else { finalize(); yyterminate(); } }
[ \t\r]+                {}
\n                      { yycolumn = 1; }

${IDENTIFIER}           { yylval.strval = strdup(yytext + 1);
                          return PARAMETER;
                        }
{IDENTIFIER}            { yylval.strval = strdup(yytext);
                          return IDENT;
                        }
\"[^"]+\"               {
                        int len = strlen(yytext);
                        char *buf = new char[len - 1];
                        memcpy(buf, yytext + 1, len - 2);
                        buf[len - 2] = '\0';
                        yylval.strval = buf;
                        return STRING;
                        }

.                       { cerr << "Unrecognized token at line " << yylineno << ":" << yycolumn << "!\n"; exit(1); }

%%

int checkfile(char *fn)
{
        int len = strlen(fn);
        struct filelist *loc;

        for (loc = filelists; loc; loc = loc->next) {
                if (strstr(loc->buf, fn))
                        return 1;
                if (len + loc->used + 2 < FILELIST_BUF_SIZE)
                        break;
        }

        if (!loc) {
                loc = (struct filelist *)malloc(sizeof(struct filelist));
                if (!loc) {
                        perror("malloc");
                        return 0;
                }
                loc->used = 0;
                loc->next = filelists;
                filelists = loc;
        }
        strcpy(loc->buf + loc->used, fn);
        loc->used += len;
        loc->buf[loc->used ++] = '$';
        loc->buf[loc->used] = '\0';
        return 0;
}

int newfile(char *fn)
{
        char buf[64];
        FILE *f;
        struct bufstack *bs;

        for (int i = 0; importPath[i] != 0; i ++) {
                snprintf(buf, 64, "%s%s.scdl", importPath[i], fn);
                f = fopen(buf, "r");
                if (f)
                        break;
        }

        if (!f) {
                perror(fn);
                return -1;
        }

        if (checkfile(buf)) {
                /* This file has been imported earlier. */
                fclose(f);
                return 0;
        }

        bs = (struct bufstack *)malloc(sizeof(struct bufstack));
        if (!bs) {
                perror("malloc");
                exit(1);
        }

        bs->prev = curbs;
        bs->bs = YY_CURRENT_BUFFER;
        bs->lineno = yylineno;
        bs->filename = curfilename;

        yy_switch_to_buffer (yy_create_buffer(f, YY_BUF_SIZE));
        bs->f = f;
        curbs = bs;
        yylineno = 1;
        curfilename = strdup(buf);;

        return 1;
}

int popfile(void)
{
        struct bufstack *bs = curbs;
        struct bufstack *prevbs;

        if (!bs)
                return 0;
        prevbs = bs->prev;

        fclose(bs->f);
        free(curfilename);
        yylineno = bs->lineno;
        curfilename = curbs->filename;

        yy_delete_buffer(YY_CURRENT_BUFFER);
        yy_switch_to_buffer(bs->bs);
        free(bs);

        curbs = prevbs;
        return 1;
}

void finalize(void)
{
        struct filelist *cur = filelists, *next;
        while (cur) {
                next = cur->next;
                free(cur);
                cur = next;
        }
        filelists = 0;
}

const char *b =
        "Assume Set : Type."
        "Assume Enum : Type."
        "Assume Bitmap : Type."
        "Assume Struct : Type."
        "Assume Number : Set."
        "Assume String : Set."
        "Assume Bool : Set."
        "Assume ListSort : Set."
        "Assume Serial : ListSort."
        "Assume Parallel : ListSort."
        "Assume Loop : ListSort.";
extern int yyparse();

void parse_builtin(void)
{
        YY_BUFFER_STATE bp;
        bp = yy_scan_string(b);
        yy_switch_to_buffer(bp);
        yyparse();
        yy_delete_buffer(bp);
}
